package com.young4j.xinsuiwutuobang.util;

import java.io.File;
import java.io.FileFilter;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 类加载器
 * 来加载该基础包名下的所有类，比如使用了某注解的类或实现了某接口的类，再或者继承了某父类的所有子类等。
 * 提供与类操作的相关方法，比如获取类加载器，加载类，获取指定包名下的所有类等。
 * @author 杨飞
 *
 */
public final class ClassUtils {
	private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtils.class);
	/**
	 * 获取类加载器
	 * 只需要获取当前线程中的ClassLoader即可
	 * 为加载类服务
	 * @return
	 */
	public static ClassLoader getClassLoader(){
		return Thread.currentThread().getContextClassLoader();
	}
	/**
	 * 加载类
	 * 加载类需要提供类名与是否初始化的标志，类加载器，这里提到的初始化是指是否执行类的静态代码块
	 * 为了他提高加载类的性能，可将loadClass方法的isInitialized设置为false
	 * @param className
	 * @param isInitialized
	 * @return
	 */
	public static Class<?> loadClass(String className,boolean isInitialized){
		Class<?> cls = null;
		try {
			cls = Class.forName(className, isInitialized, getClassLoader());
		} catch (ClassNotFoundException e) {
			LOGGER.error("load class failure",e);
			e.printStackTrace();
		}
		return cls;
	}
	/**
	 * 获取指定包名下的所有类
	 * 需要将包名转换为文件路径，读取class文件或jar包，获取指定类名去加载类
	 * 来获取文件路径时，里面的路径空格会被“%20”代替，所以我们要将20%替换成“”
	 * @param packageName
	 * @return
	 */
	public static Set<Class<?>> getClassSet(String packageName){
		Set<Class<?>> classSet = new HashSet<Class<?>>();
		try {
			Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
			while(urls.hasMoreElements()){
				URL url = urls.nextElement();
				if(url != null){
					String protocol = url.getProtocol();
					if(protocol.equals("file")){
						String packagePath = url.getPath().replaceAll("%20", "");
						addClass(classSet,packagePath,packageName);
					}else if(protocol.equals("jar")){
						JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
						if(jarURLConnection != null){
							JarFile jarFile = jarURLConnection.getJarFile();
							if(jarFile != null){
								Enumeration<JarEntry> jarEntries = jarFile.entries();
								while(jarEntries.hasMoreElements()){
									JarEntry jarEntry = jarEntries.nextElement();
									String jarEntryName = jarEntry.getName();
									if(jarEntryName.endsWith(".class")){
										String className = jarEntryName.substring(0,jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
										doAddClass(classSet,className);
									}
								}
							}
						}
					}
				}
			}
		} catch (Exception e) {
			LOGGER.error("get class set failure",e);
			throw new RuntimeException(e);
		}
		return classSet;
	}
	private static void addClass(Set<Class<?>> classSet,String packagePath,String packageName){
		File[] files = new File(packagePath).listFiles(new FileFilter() {
			
			@Override
			public boolean accept(File file) {
				return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
			}
		});
		for(File file:files){
			String fileName = file.getName();
			if(file.isFile()){
				String className = fileName.substring(0,fileName.lastIndexOf("."));
				if(StringUtils.isNotBlank(packageName)){
					className = packageName + "." + className;
				}
				doAddClass(classSet,className);
			}else {
				String subPackagePath = fileName;
				if(StringUtils.isNotBlank(packageName)){
					subPackagePath = packagePath + "/" + subPackagePath;
				}
				String subPackageName = fileName;
				if(StringUtils.isNotBlank(packageName)){
					subPackageName = packageName + "." + subPackageName;
				}
				addClass(classSet,subPackagePath,subPackageName);
			}
		}
	}
	private static void doAddClass(Set<Class<?>> classSet,String className){
		Class<?> cls = loadClass(className, false);
		classSet.add(cls);
	}
}
